/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.rest.api.exception;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.iec.edp.caf.common.JSONSerializer;
import io.iec.edp.caf.commons.event.StartupCompletedEvent;
import io.iec.edp.caf.commons.event.StartupCompletedTask;
import io.iec.edp.caf.commons.exception.CAFRuntimeException;
import io.iec.edp.caf.commons.exception.entity.ExceptionContext;
import io.iec.edp.caf.commons.exception.ExceptionLevel;
import io.iec.edp.caf.commons.exception.handler.CAFRuntimeServerException;
import io.iec.edp.caf.commons.exception.service.CafExceptionHandler;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.rest.api.event.RestExceptionEvent;
import io.iec.edp.caf.rest.api.event.RestExceptionEventArgs;
import io.iec.edp.caf.rest.api.event.RestExceptionPublisher;
import lombok.var;
import org.springframework.dao.DataAccessException;

import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.time.OffsetDateTime;
import java.util.UUID;

/**
 * This is {@link RESTServiceExceptionMapper}.
 * @author Yi Siqi
 * @since 0.0.1
 */

public class RESTServiceExceptionMapper implements ExceptionMapper<Throwable> {

    @Context
    HttpHeaders headers;
    //	@Autowired
    private CafExceptionHandler exceptionHandling;

    private String env;

    public RESTServiceExceptionMapper() {

    }

    @Override
    public Response toResponse(Throwable throwable) {
        Exception exception=new Exception();
        //TODO：临时先用Guid生成一个请求Id
        String requestId = UUID.randomUUID().toString();
        //		exception = new CAFRuntimeException("sys","ECP_CAF_EXCP_0101","测试业务异常",null,null, ExceptionLevel.Warning,true);
        WebApiException apiException = new WebApiException();

        if(throwable instanceof Exception){
            exception = this.getExceptionHandling().handle(new ExceptionContext((Exception) throwable, null));
        }

//        if (exception instanceof BaseException) {
//            BaseException baseException = (BaseException) exception;
//            apiException.setCode(baseException.getExceptionCode());
//            apiException.setLevel(baseException.getLevel().ordinal());
////			apiException.setLevel(ExceptionLevel.Warning.ordinal());
//            apiException.setRequestId(requestId);
//            apiException.setMessage(baseException.getMessage());
//        } else
        if(throwable instanceof Error){
            apiException.setLevel(ExceptionLevel.Error.ordinal());
            apiException.setRequestId(requestId);
            apiException.setMessage(throwable.getMessage());
            apiException.setInnerMessage(getInnerExceptionMessage(throwable));
        }else if (exception instanceof CAFRuntimeException) {
            CAFRuntimeException runtimeException = (CAFRuntimeException) exception;
            apiException.setCode(runtimeException.getExceptionCode());
            apiException.setLevel(runtimeException.getLevel().ordinal());
//			apiException.setLevel(ExceptionLevel.Warning.ordinal());
            apiException.setRequestId(requestId);
            apiException.setMessage(runtimeException.getMessage());
            apiException.setExtensionMessage(runtimeException.getExtensionMessage());
            apiException.setInnerMessage(getInnerExceptionMessage(runtimeException));
        } else if (exception instanceof DataAccessException) {
            apiException.setLevel(ExceptionLevel.Error.ordinal());
            apiException.setRequestId(requestId);
            String wrapMessage = removeSQLScript(exception.getMessage());
            apiException.setMessage(wrapMessage);
            apiException.setInnerMessage(getInnerExceptionMessage(exception));
        } else {
            apiException.setLevel(ExceptionLevel.Error.ordinal());
            apiException.setRequestId(requestId);
            apiException.setMessage(exception.getMessage());
            apiException.setInnerMessage(getInnerExceptionMessage(exception));
        }

        apiException.setDate(OffsetDateTime.now());

        //TODO:此处后续可以根据环境，决定是否把详细堆栈返回前端
        var profiles = SpringBeanUtils.getApplicationContext().getEnvironment().getActiveProfiles();
        this.env = profiles.length > 0 ? profiles[0] : "prod";
        if (env.equals("dev") || env.equals("test"))
            apiException.setDetail(getException(exception));
        //else if(env.equals("prod"))
        //apiException.setDetail(exception.getMessage());
        //apiException.setDetail(getException(exception));
        ObjectMapper mapper = JSONSerializer.getObjectMapper();
        String exceptionJson = "";
        try {
            exceptionJson = mapper.writeValueAsString(apiException);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        if(throwable!=null){
            //触发rest的exception事件,主要用于在prod模式下，通知外界订阅者，告诉其详细异常
            RestExceptionPublisher eventPublisher = SpringBeanUtils.getBean(RestExceptionPublisher.class);
            RestExceptionEventArgs args = new RestExceptionEventArgs();
            args.setThrowable(throwable);
            eventPublisher.onHandleException(new RestExceptionEvent(args));
        }

        return Response
                .status(parseStatusCode(exception))
                .entity(exceptionJson)
                .type(MediaType.APPLICATION_JSON_TYPE)
                .encoding("UTF-8")
                .build();
    }

    /**
     * 移除Message中的sql语句
     *
     * @param message
     * @return
     */
    private String removeSQLScript(String message) {
        if (message.contains("SQL [")) {
            String sql = message.substring(message.indexOf("SQL ["), message.lastIndexOf("]") + 1);
            message = message.replace(sql, "SQL [*]");
        }

        return message;
    }

    private CafExceptionHandler getExceptionHandling() {
        if (this.exceptionHandling == null)
            this.exceptionHandling = SpringBeanUtils.getBean(CafExceptionHandler.class);
        return this.exceptionHandling;
    }

    private Status parseStatusCode(Exception exception) {
        ExceptionResponseStatus statusAnnotation = exception.getClass().getAnnotation(ExceptionResponseStatus.class);
        return statusAnnotation != null
                ? statusAnnotation.value()
                : Status.INTERNAL_SERVER_ERROR;
    }

    /**
     * 将 Exception 转化为 String
     */
    /**
     * 将异常日志转换为字符串
     *
     * @param e
     * @return
     */
    private String getException(Exception e) {
        Writer writer = null;
        PrintWriter printWriter = null;
        try {
            writer = new StringWriter();
            printWriter = new PrintWriter(writer);
            e.printStackTrace(printWriter);
            return writer.toString();
        } finally {
            try {
                if (writer != null)
                    writer.close();
                if (printWriter != null)
                    printWriter.close();
            } catch (IOException e1) {
            }
        }
    }

    private String getInnerExceptionMessage(Throwable e){
        var s = "";
        if(e!=null){
            if(e instanceof CAFRuntimeServerException){
                var targetEx = e.getCause();
                if(targetEx!=null && targetEx.getCause()!=null){
                    s = targetEx.getCause().getMessage();
                }
            }else{
                if(e.getCause()!=null){
                    s = e.getCause().getMessage();
//            先不递归了
//            s = e.getMessage()+","+getInnerExceptionMessage(e.getCause());
                }
//            else{
//                s = e.getMessage();
//            }
            }

        }
        return s;
    }
}
